*******************
Command-line client
*******************

SYNOPSIS
========

:command:`sievemgr` [:option:`server`] [:option:`command`]
[:option:`argument` ...]

:command:`sievemgr` :option:`-e expression` [...] [:option:`server`]

:command:`sievemgr` :option:`-s file` [:option:`server`]

:command:`sievemgr` :option:`-h`

:command:`sievemgr` :option:`-V`


DESCRIPTION
===========

:command:`sievemgr` is a command-line client for uploading, downloading,
and managing Sieve scripts using the ManageSieve protocol. By default,
a shell is entered and commands are read from standard input;
the shell supports tab-completion. See COMMANDS_ below for a list.

Commands can also be given either on the command line, as `expression`
with :option:`-e`, or read from a `file` given with :option:`-s`.
The shell is not entered if commands are given.

.. only:: man

    The `server` defaults to the :confvar:`host`
    set in :manpage:`sieve.cf(5)` or :samp:`localhost`.

.. only:: not man

    The `server` defaults to the :confvar:`host`
    set in :doc:`sieve.cf <config>` or :samp:`localhost`.


OPERANDS
========

.. option:: server

    URL of the form
    :samp:`[sieve://][{login}[:{passwd}]@]{host}[:{port}][/{owner}]`.

    `login`
        .. only:: man

            defaults to the :confvar:`login` set for `host` in
            :manpage:`sieve.cf(5)` or :file:`.netrc`,
            or to the current user.

        .. only:: not man

            defaults to the :confvar:`login` set for `host`
            in :doc:`sieve.cf <config>` or :file:`.netrc`,
            or to the current user.

    `passwd`
        is prompted for by default (see LOGIN_ for automation).

    `port`
        defaults to 4190 (the standard port for ManageSieve).

    `owner`
        defaults to `login`.

    .. danger::

        Other users can see passwords given on the command line.

.. option:: command

    Command to run (see COMMANDS_ below).

.. option:: argument

    Argument to that command.


OPTIONS
=======

.. option:: -C

    Do *not* overwrite files.

.. option:: -N file

    Use `file` as :file:`.netrc` file.

.. option:: -V

    Print version.

.. option:: -c file

    Read configuration from `file`.

.. option:: -d

    Enable debugging mode.

.. option:: -e expression

    Execute `expression` on the `server`.

.. option:: -f

    Overwrite and remove files without confirmation.

.. option:: -h

    Print help.

.. option:: -i

    Confirm removing or overwriting files.

.. option:: -o key=value

    Set the configuration `key` to `value`.

    :samp:`-o {key}=yes` can be shortened to :samp:`-o {key}`.
    :samp:`-o {key}=no` can be shortened to :samp:`-o no{key}`.

    .. only:: man

        See :manpage:`sieve.cf(5)` for a list of keys.

    .. only:: not man

        See :doc:`sieve.cf <config>` for a list of keys.

.. option:: -q

    Be quieter.

.. option:: -s file

    Execute expressions read from `file`.

.. option:: -v

    Be more verbose.

:option:`-c`, :option:`-e`, :option:`-o`, :option:`-q`, and :option:`-v`
can be given multiple times.


COMMANDS
========

.. sievecmd:: ! [command] [argument ...]

    Shorthand for :sievecmd:`sh`.

.. sievecmd:: ? [command]

    Shorthand for :sievecmd:`help`.

.. sievecmd:: about

    Print information about SieveManager.

.. sievecmd:: activate script

    Marks `script` as the active script.
    This is the script that the mail server will run for incoming mail.
    Only one script can be active at a time.

.. sievecmd:: caps

    Print the server's capabilities (output is YAML_).

.. sievecmd:: cat [script ...]

    Print the given scripts to standard output.

.. sievecmd:: cd [localdir]

    Change to local working directory to `localdir`,
    which defaults to the current user's home directory.

.. sievecmd:: cert

    Print information about the server's TLS certificate (output is YAML_).

.. sievecmd:: check localscript

    Check whether `localscript` is semantically valid.

.. sievecmd:: cmp script1 [...] scriptN

    Check whether scripts are equal.

.. sievecmd:: cp [-f|-i] source target

    Download `source` and re-upload it as `target`.

    .. rubric:: Options:

    -f  Overwrite `target` without confirmation.
    -i  Ask for confirmation before overwriting `target`.

.. sievecmd:: deactivate

    Deactivate the active script

.. sievecmd:: diff [-C n|-U n|-c|-u] [-b] script1 script2

    Show how `script1` and `script2` differ.

    .. rubric:: Options:

    -C n  Show `n` lines of copied context.
    -U n  Show `n` lines of unified context.
    -b    Ignore whitespace before a linefeed.
    -c    Show three lines of copied context.
    -u    Show three lines of unified context.

.. sievecmd:: echo word [...]

    Print `word` to standard output.

.. sievecmd:: ed [-a] script [...]

    Download `script`, edit it with a line editor, and re-upload it.

    .. rubric:: Options:

    -a  Edit the active script (any `script` given is ignored).

.. sievecmd:: exit

    Log out and exit.

.. sievecmd:: get [-f|-i] [-a] [-o file] [script ...]

    Download scripts.

    .. rubric:: Options:

    -a       Download the active script only.
    -f       Overwrite files without confirmation.
    -i       Ask for confirmation before overwriting a file.
    -o file  Save `script` as :file:`file`.

.. sievecmd:: help [command]

    Print help for `command` or
    list commands if `command` is omitted.
    Can be shortened to :sievecmd:`?`. 

    For example:

    .. code:: none

        sieve://user@imap.foo.example> ?ls
        ls [script ...] - list scripts

.. sievecmd:: ls [-1al] [script ...]

    List the given scripts or all scripts if no `script` is given.
    The active script is marked with an asterisk ("\*").

    .. rubric:: Options:

    -1  List one script per line.
        Implied if standard input is not a terminal.

    -a  List the active script only.

    -l  List one flag-script name pair per line, separated by whitespace,
        where the flag is one of:

        :literal:`a`
            Script is active.

        :literal:`e`
            End-of-transmission mark.
            *Not* followed by a script name.

        If neither flag applies, a dash ("-") is printed instead.

    The active script is *not* marked with an asterisk if
    ``-1``, ``-a``, or ``-l`` is given.

    .. rubric:: Examples:

    .. code:: none

        sieve://user@imap.foo.example> ls
        bar.sieve foo.sieve*

    .. code:: none

        sieve://user@imap.foo.example> ls -l
        - bar.sieve
        a foo.sieve
        e

.. sievecmd:: more [-aceis] [script ...]

    Display scripts page-by-page.

    .. rubric:: Options:

    -a          Display the active script only.
    -c          Clear screen instead of scrolling.
    -e          Exit immediately after writing the last line.
    -i          Ignore case in pattern matching.
    -s          Treat consecutive empty lines as a single empty line.

.. sievecmd:: mv [-f|-i] source target

    Rename `source` to `target`.

    .. rubric:: Options:

    -f  Replace `target` without confirmation.
    -i  Ask for confirmation before replacing `target`.

.. sievecmd:: put [-f|-i] [-a] [-o name] [localscript ...]

    Upload scripts.

    -a       Replace the active script or, if no script is active
             or if ``-o`` has been given *after* ``-a``, activate
             the script after uploading.
    -f       Replace scripts without confirmation.
    -i       Ask for confirmation before replacing a script.
    -o name  Upload `localscript` as `name`.

    The server should reject syntactically invalid scripts.
    It may issue a warning for semantically invalid scripts,
    but should accept them nonetheless. Updates are atomic.

.. sievecmd:: python

    Enter a Python read-evaluate-print loop, with the
    :mod:`SieveManager <sievemgr.SieveManager>` object
    that represents the connection as namespace.

.. sievecmd:: rm [-f|-i] [script ...]

    Remove scripts.

    -f  Remove scripts without confirmation.
    -i  Ask for confirmation before removing a script.

.. sievecmd:: sh [command] [argument ...]

    Run system `command`. If `command` is omitted, enter a system shell.
    Can be shortened to :sievecmd:`!`.

    .. rubric:: Examples:

    .. code:: none

        sieve://user@imap.foo.example> cd sieve
        sieve://user@imap.foo.example> !pwd
        /home/user/sieve
        sieve://user@imap.foo.example> !ls
        foo.sieve bar.sieve
        sieve://user@imap.foo.example> put foo.sieve

    .. code:: none

        sieve://user@imap.foo.example> !
        bash-2.0$

.. sievecmd:: su user

    Manage the scripts of `user`.
    Requires elevated privileges.

.. sievecmd:: vi [-a] script [...]

    Download `script`, edit it with a visual editor, and re-upload it.

    .. rubric:: Options:

    -a  Edit the active script (any `script` given is ignored).

.. sievecmd:: xargs command [arg ...]

    Call `command` with the given arguments and each line from
    standard input as additional argument up to, but excluding,
    the first empty line or the end-of-file mark. The lines
    read from standard input are neither subject to
    `WORD SPLITTING`_ nor to `PATTERN EXPANSION`_. 


WORD SPLITTING
==============

Lines are split into words at any run of consecutive whitespace.
If a filename contains whitespace, that whitespace must either be
escaped with a backslash ("\\") or the filename must be quoted.

For example,

.. code:: none

    sieve://user@imap.foo.example> get foo bar

downloads the two files :file:`foo` and :file:`bar`.

But

.. code:: none

    sieve://user@imap.foo.example> get "foo bar"

downloads the single file :file:`foo bar`.

If a filename contains backslashes or quotes (single or double),
they must be escaped. For example,

.. code:: none

    sieve://user@imap.foo.example> get foo\\bar\'

downloads the file :file:`foo\\bar'`.

See :manpage:`sh(1)` and :manpage:`wordexp(3)` for details.


PATTERN EXPANSION
=================

If :samp:`*`, :samp:`?`, or :samp:`[{chars}]` occur in an expression given
with :option:`-e`, read from a script given with :option:`-s`, or read from
standard input, they are expanded to local or remote filenames in the same
way as a they would be expanded by a system shell. If a command operates on
local scripts, patterns are expanded to matching filenames on the local
system; if a command operates on remote scripts, patterns are expanded to
matching filenames on the remote system.

For example,

.. code:: none

    sieve://user@imap.foo.example> put *.sieve

uploads every *local* file that matches :samp:`*.sieve`.

But

.. code:: none

    sieve://user@imap.foo.example> get *.sieve

downloads every *remote* file that matches :samp:`*.sieve`.

If a filename contains :samp:`*`, :samp:`?`, or :samp:`[{chars}]`, those
characters must be escaped with a backslash ("\\") or the filename as a
whole must be quoted. For example,

.. code:: none

    sieve://user@imap.foo.example> get *.sieve

downloads every file that matches the *pattern* :samp:`*.sieve`.

But

.. code:: none

    sieve://user@imap.foo.example> get "*.sieve"

downloads the *file* :file:`*.sieve`.

See :manpage:`sh(1)` and :manpage:`fnmatch(3)` for details.


SCRIPTING
=========

Operations can be scripted by giving a :option:`command`,
redirecting standard input, or with :option:`-e` or :option:`-s`.


Basics
------

Scripts abort if an error occurs, so errors must be prevented.

Confirmation is always prompted for on the controlling terminal,
regardless of input/output redirection. If there is no controlling
terminal, operations that require confirmation trigger an error.

Comments start with a '#' and are ignored.


Comparing Scripts
-----------------

:sievecmd:`cmp` can be used to compare remote scripts.

.. code:: bash

    $ if sievemgr user@imap.foo.example cmp -s foo.sieve bar.sieve
    > then echo 'foo.sieve and bar.sieve are equal'
    > else echo 'foo.sieve and bar.sieve differ'
    > fi

.. code:: bash

    $ case $(sievemgr user@imap.foo.example cmp foo.sieve bar.sieve) in
    > (*equal)   echo 'foo.sieve and bar.sieve are equal' ;;
    > (*differs) echo 'foo.sieve and bar.sieve differ' ;;
    > esac


Listing Scripts
---------------

:sievecmd:`ls` prints one script name per line if standard input
is not a terminal. Sieve script names must not contain linefeeds,
so :sievecmd:`ls` can safely be used in scripts.

.. code:: bash

    $ sievemgr -e'ls -a' -e'deactivate' user@imap.foo.example |
    > sievemgr user@imap.foo.example xargs rm -f

.. code:: bash

    $ mkfifo pipe
    $ sievemgr user@imap.foo.example ls -l >pipe & pid=$!
    $ nscripts=0
    $ while read -r _ script && [ "$script" ]
    > do
    >     eval "script_${nscripts}"='$script'
    >     nscripts=$((nscripts + 1))
    > done <pipe
    $ wait $pid


Persistent Connections
----------------------

A connection is opened each time :command:`sievemgr` is called. So
if multiple messages are going to be exchanged between the client and
the server, it is more efficient to run :command:`sievemgr` in the
background and send and receive messages through pipes than to
call :command:`sievemgr` for each exchange.

Create one pipe for sending commands and another one for receiving responses:

.. code:: bash

    $ mkfifo -m 0600 send recv

Start :command:`sievemgr` and redirect its input and output to these pipes:

.. code:: bash

    $ sievemgr user@imap.foo.example <send >recv & pid=$!

Open the pipes in the shell:

.. code:: bash

    $ exec 3>send
    $ exec 4<recv

Commands can now be sent by writing them to file descriptor (FD) 3:

.. code:: bash

    $ echo ls -l >&3

And responses can be read from FD 4:

.. code-block:: bash
    :emphasize-lines: 6

    $ nscripts=0
    $ while read -r _ script && [ "$script" ]
    > do
    >     eval "script_${nscripts}"='$script'
    >     nscripts=$((nscripts + 1))
    > done <&4

However, be careful to avoid deadlocks:

.. code-block:: bash
    :caption: Deadlock
    :emphasize-lines: 3

    echo ls -a >&3
    # read will wait forever if there is no active script.
    active="$(read <&4)"

.. code-block:: bash
    :caption: Deadlock
    :emphasize-lines: 4

    echo ls -l >&3
    nscripts=0
    # The loop reads past the output of ls -l and then waits forever.
    while read -r _ script
    do
        eval "script_${nscripts}"='$script'
        nscripts=$((nscripts + 1))
    done <&4

The output of SieveManager commands must be checked
for an end-of-transmission mark:

======================  ==================================
Command                 End-of-transmission mark
======================  ==================================
:sievecmd:`caps`        ``...``
:sievecmd:`cert`        ``...``
:sievecmd:`cmp`         Ends with ``equal`` or ``differs``
:sievecmd:`ls -l <ls>`  First word is ``e``
======================  ==================================

Also be careful to avoid races:

.. code-block:: bash
    :caption: Race condition
    :emphasize-lines: 3

    echo get foo.sieve >&3
    # patch will likely run BEFORE get has downloaded foo.sieve.
    patch foo.sieve <patchfile

Use :sievecmd:`echo` to wait for previously sent commands to finish:

.. code-block:: bash
    :emphasize-lines: 3, 5-7

    $ cat <<EOF >&3
    > get foo.sieve
    > echo fin
    > EOF
    $ while read -r line && [ "$line" != fin ]
    > do :
    > done <&4
    $ patch foo.sieve <patchfile

:command:`read` blocks, so this is *not* a busy wait.

Exit by sending :sievecmd:`exit`.

.. code:: bash

    $ echo exit >&3
    $ wait "$pid"

But abort by:

.. code:: bash

    $ kill "$pid"
    $ wait "$pid"

Exiting by writing :sievecmd:`exit` to FD 3, instead of by sending a
:literal:`TERM` with :command:`kill`, makes sure that :command:`sievemgr`
exits only after it has finished executing previously sent commands.
Conversely, aborting with :command:`kill` makes sure that
:command:`sievemgr` exits right away.

See :download:`examples/sievepatch <../examples/sievepatch>`
for an extended example.


LOGIN
=====

.. only:: man

    Logins can be automated by reading passwords from the
    standard output of a command, from :manpage:`sieve.cf(5)`,
    or from :file:`.netrc`, or by using TLS client authentication.

.. only:: not man

    Logins can be automated by reading passwords from the
    standard output of a command, from :doc:`sieve.cf <config>`,
    or from :file:`.netrc`, or by using TLS client authentication.

Files that contain passwords must be neither group- nor world-readable.

.. danger::

   Password should be stored in encrypted form only.
   Prefer using a password manager over :file:`sieve.cf` or :file:`.netrc`.


Password Managers
-----------------

Set :confvar:`getpassword` to a :samp:`{command}` to read the password
from the standard output of that *command*.

For example, add

.. code:: none

    getpassword pass $login@$host

.. only:: man

    to your :manpage:`sieve.cf(5)` to query pass_
    for the password for the current `host`.

.. only:: not man

    to your :doc:`sieve.cf <config>` to query pass_
    for the password for the current `host`.

:samp:`$host` and :samp:`$login` are expanded to the given `host`
and the login for that `host` respectively.


The :file:`sieve.cf` File
-------------------------

Set :confvar:`password` to a :samp:`{word}` to log
in using that :samp:`{word}` as your password.

For example, add

.. code:: none

    account imap.foo.example
        login user
        password pencil

.. only:: man

    to your :manpage:`sieve.cf(5)` to automatically
    log in as :samp:`user` with the password :samp:`pencil`
    on :samp:`imap.foo.example`.

.. only:: not man

    to your :doc:`sieve.cf <config>` to automatically
    log in as :samp:`user` with the password :samp:`pencil`
    on :samp:`imap.foo.example`.


The :file:`.netrc` File
-----------------------

The :file:`.netrc` file is a traditional facility to automate logins.

For example, add

.. code:: none

    machine imap.foo.example
        login user
        password pencil

to your :file:`.netrc` to automatically log in as :samp:`user` with
the password :samp:`pencil` on :samp:`imap.foo.example`.

See the GNU Inetutils manual (chap. `11.7 <netrc_>`_) for details.


TLS Client Authentication
-------------------------

There are two types of TLS client authentication. Sending a TLS client
certificate may be required for another form of authentication to be
permitted or the user may be authenticated *by* sending a certificate.

Both types require sending a TLS client certificate. Set :confvar:`cert`
to a :file:`{file}` that contains a TLS key and a TLS certificate
to decrypt messages with that key and send that certificate:

.. code:: none

    account imap.foo.example
        login user
        cert cert.pem

In addition, set :confvar:`saslmechs` to ``external`` to authenticate *by*
sending that certificate:

.. code:: none

    account imap.foo.example
        login user
        cert cert.pem
        saslmechs external

.. only:: man

    See :manpage:`sieve.cf(5)` for details.

.. only:: not man

    See :doc:`sieve.cf <config>` for details.


EXIT STATUS
===========

0
    Success

1
    Failure

2
    Usage error


ENVIRONMENT
===========

.. envvar:: COLUMNS

    Terminal width in characters.

.. envvar:: EDITOR

    Editor called by :sievecmd:`ed` (default: :command:`ed`).

.. envvar:: HOME

    Home directory of the current user.

.. envvar:: LANG, LC_ALL, LC_CTYPE

    Encoding for reading from/writing to the terminal and applications. Do
    *not* apply to Sieve scripts, which must be encoded as UTF-8. Order of
    preference is :envvar:`LC_ALL` > :envvar:`LC_CTYPE` > :envvar:`LANG`.

.. envvar:: LINES

    Terminal height in lines.

.. envvar:: LOGNAME

    Login name of the current user.

.. envvar:: PAGER

    Pager called by :sievecmd:`more` (default: :command:`more`).

.. envvar:: NETRC

    Filename of the :file:`.netrc` file
    (default: :file:`{$HOME}/.netrc`).

.. envvar:: VISUAL

    Editor called by :sievecmd:`vi` (default: :command:`vi`).

.. envvar:: XDG_CONFIG_HOME

    X Desktop Group base configuration directory
    (default: :file:`{$HOME}/.config`).


FILES
=====

.. index:: pair: /etc/sieve/config; file
.. index:: pair: /etc/sieve.cf; file
.. index:: pair: $XDG_CONFIG_HOME/sieve/config; file
.. index:: pair: $HOME/.sieve/config; file
.. index:: pair: $HOME/.sieve.cf; file

:file:`/etc/sieve/config`, :file:`/etc/sieve.cf`, :file:`{$XDG_CONFIG_HOME}/sieve/config`, :file:`{$HOME}/.sieve/config`, :file:`{$HOME}/.sieve.cf`
    Default configuration files. Not read when :option:`-c` is given.

    .. only:: man

        See :manpage:`sieve.cf(5)` for details.

    .. only:: not man

        See :doc:`sieve.cf <config>` for details.

.. index:: pair: .netrc; file

:file:`.netrc`
    Login information.


STANDARDS
=========

:rfc:`2195` (CRAM-MD5)

:rfc:`2244` (ACAP)

:rfc:`2782` (SRV records)

:rfc:`4013` (SASLprep)

:rfc:`4422` (SASL)

:rfc:`4616` (PLAIN)

:rfc:`5228` (Sieve) 

:rfc:`5802` (SCRAM)

:rfc:`5804` (ManageSieve)

:rfc:`5019` (Lightweight OCSP)

:rfc:`6960` (OCSP)

:rfc:`7677` (SCRAM-SHA-256 and SCRAM-SHA-256-PLUS)


SECURITY
========

Credentials are stored in memory so that they need not be entered again
in case of a referral. However, because page-locking is unfeasible in
Python, they may be swapped out to the disk.

Passwords can be queried from password managers to automate logins. However,
any command that can be run by :command:`sievemgr` can, at the very least,
also be run by any application that can run :command:`python`.


BUGS
====

:file:`.netrc` records without a ``password`` token wrongly trigger
a parse error in Python up to version 3.9.


EXAMPLES
========

Upload :file:`script.sieve` to :samp:`imap.foo.example` and activate it:

.. code:: none

    $ sievemgr user@imap.foo.example
    sieve://user@imap.foo.example> put script.sieve
    sieve://user@imap.foo.example> activate script.sieve
    sieve://user@imap.foo.example> exit

Edit the active script on :samp:`imap.foo.example`:

.. code:: bash

    $ sievemgr user@imap.foo.example vi -a

Reading commands from standard input:

.. code:: bash

    $ sievemgr user@imap.foo.example <<EOF
    > put script.sieve
    > activate script.sieve
    > EOF

Download all scripts from :samp:`imap.foo.example`:

.. code:: bash

    $ sievemgr -e'get *' user@imap.foo.example


.. only:: man

    SEE ALSO
    ========

    :manpage:`sieve.cf(5)`

